package Parser;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;

import LL1checker.BNF;
import LL1checker.Expression;
import LL1checker.NonTerminal;
import LL1checker.Term;
import LL1checker.Terminal;

/** 
 * De parser herkent zinnen van een simpele grammatica.
 * Deze grammatica is G(&lt;expression&gt;)<br><br>
 * &nbsp;&lt;expression&gt; ::= &lt;term&gt; &lt;expression-tail&gt;<br>
 * &nbsp;&lt;expression-tail&gt; ::= e | &lt;plus-expression&gt; | &lt;minus-term&gt;<br>
 * &nbsp;&lt;plus-expression&gt; ::= PLUS &lt;expression&gt;<br>
 * &nbsp;&lt;minus-term&gt; ::= MINUS &lt;term&gt; &lt;expression-tail&gt;<br>
 * &nbsp;&lt;term&gt; ::= &lt;factor&gt; &lt;term-tail&gt;<br>
 * &nbsp;&lt;term-tail&gt; ::= e | &lt;times-term&gt;<br>
 * &nbsp;&lt;times-term&gt; ::= TIMES &lt;term&gt;<br>
 * &nbsp;&lt;factor&gt; ::= PARENT_LEFT &lt;expression&gt; PARENT_RIGHT<br>
 * &nbsp;&lt;factor&gt; ::= ID | INT | LONG | FLOAT | DOUBLE<br>
 * [Namen in hoofdletters zijn tokens die door de scanner herkend worden.]<br>
 * Het resultaat van parsen is een string representatie van de oorspronkelijke
 * expressie met daaraan haakjes toegevoegd die de volgorde van evaluatie
 * duidelijk maakt. Bijvoorbeeld, de invoer expressie <code>1+2*3</code>
 * resulteert in <code>(1+(2*3))</code>, omdat de vermenigvuldig operator
 * sterker bindt (en dus eerder wordt ge&euml;valueerd) dan de optel operator.
 */
class Parser implements TokenKinds {

  private Scanner scanner;
  private Token lookahead;
  private BNF bnf;

  /**
   * Een fout is opgetreden bij het parsen van de nonterminal.
   * @param nonterminal naam van nonterminal
   * @param message foutmelding
   */
  private void parseError(String nonterminal,String message) {
    System.err.println();
    System.err.println("parse error in <"+nonterminal+"> : "+message);
    System.err.print("lookahead '"+lookahead.getType()+"' ");
    System.err.print("(kind="+lookahead.getType()+") ");
    System.err.print("at "+lookahead.getLine()+"/"+lookahead.getColumn());
    System.err.println();
    throw new RuntimeException();
  }

  /**
   * Accepteer huidige token van scanner.
   */
  private void acceptIt() {
    try {
      lookahead=scanner.nextToken();
    } catch (IOException exception) {
      exception.printStackTrace();
      throw new RuntimeException();
    }
  }
 
  /**
   * Accepteer huidige token van scanner, mits deze van een bepaald soort is.
   * @param nonterminal naam van nonterminal
   * @param kind identifatie van token soort
   */
  private void accept(String nonterminal,int type) {
    if ( lookahead.getType()!=type ) {
      parseError(nonterminal,"type="+type+" expected");
    } else {
      acceptIt();
    }
    
  }

  /**
   * Construeer een nieuwe parser op de input stream.
   * @param in input stream van characters
   */
  public Parser(InputStream in) {
    scanner=new Scanner(new BufferedReader(new InputStreamReader(in)));
    //Initialiseer lookahead met eerste token van scanner
    acceptIt();
  }

  /**
   * Parse het root symbool van de grammatica.
   * @return het resultaat van parsing (in dit geval een string)
   */
	public ArrayList<Expression> parseRoot() {
		ArrayList<Expression> expressions = new ArrayList<Expression>();
		while (lookahead.getType() != EOF) {
			expressions.add(parseExpression());
			acceptIt();
		}
		return expressions;
	}

	private Expression parseExpression() {
		Expression expr = new Expression();
		expr.setNonTerminal(parseNonTerminal());
		accept("DefinedAs", ISDEFINEDAS);
		expr.setAlternatives(parseAlternatives());
		accept("EOL", EOL);
		return expr;
	}
	
	
    private NonTerminal parseNonTerminal() {
        NonTerminal nonTerminal = null;
        if (lookahead.getType() == NONTERMINAL) {
                nonTerminal = new NonTerminal(lookahead.getValue());
                acceptIt();
        } else {
        	parseError("nonterminal", "expected nonterminal");
        }
        return nonTerminal;
    }
    
    private ArrayList<Term> parseAlternatives(){
    	ArrayList<Term> alternatives = new ArrayList<Term>();
    	
    	//TODO PARSE ALTERNATIVES
    	
    	return alternatives;    	
    }


        
    private Term parseTerm() {
        Term term = null;
        switch(lookahead.getType()) {
        case NONTERMINAL:
                term = new NonTerminal(lookahead.getValue());
                acceptIt();
                break;
        case TERMINAL:
                term = new Terminal(lookahead.getValue());
                acceptIt();
                break;
        default:
        	parseError("term", "expected terminal or nonterminal ");
        }
        return term;
    }
    

    private boolean isTerm(){
    	if (lookahead.getType() == NONTERMINAL || lookahead.getType() == TERMINAL  ){
    		return true;
    	} else {
    		return false;
    	}
    }
}


